/*================================================================
*   Copyright (C) 2018 Sangfor Ltd. All rights reserved.
*   
*   文件名称：main.c
*   创 建 者：yysshh
*   创建日期：2018年12月27日
*   描    述：
*
================================================================*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<termios.h>
#include<fcntl.h>
#include<string.h>
#include<time.h>

#define WIDTH 21             					
#define HEIGHT 16									
#define MOVETO(x,y) printf("\033[%d;%dH",(y),(x))	
#define L_BLACK                 "\e[1;30m"			
#define NONE                    "\e[0m"

int gameover = 0;									
int shadow_flag = 0;								//shadow on/off
int food_length = 1;							
int snake_length = 3;								
int scores = 50;									
int speed = 10;										

struct termios oldt, newt;							
int oldf;

typedef enum{UP = -1, DOWN = 1 , LEFT = 2, RIGHT = 4}DIR; //direction
DIR cur_dir = LEFT;

char FOOD_PIC[4][4] = {{"✶"},{"❉"},{"☯"},{"✡"}};		  //food picture

typedef struct snake_node_data
{
	int x;
	int y;
	DIR dir;
}Date, *PDate;

typedef struct snake_node
{
	Date date;
	struct snake_node *pnext;
}Snake, *PSnake;

typedef struct food_node_data
{
	int x;
	int y;
	int pic;
}FDate, *PFDate;

typedef struct food_node
{
	FDate date;
	struct food_node *pnext;
}Food, *PFood;

/*draw game area
 *
 *parameter:
 *
 *return:
 *
 */
void draw_ground()
{
	int i;
	system("clear");
	for(i = 0 ; i < (WIDTH - 1)* 2 ; i++)
	{
		MOVETO(10 + i, 5);	
		printf("━");
	}
	for(i = 0 ; i < HEIGHT ; i++)
	{
		MOVETO(10, 6 + i);
		printf("┃");
		MOVETO(50, 6 + i);
		printf("┃");
	}
	for(i = 0 ; i < (WIDTH - 1)* 2 ; i++)
	{
		MOVETO(10 + i, 22);	
		printf("━");
	}

	MOVETO(10,5);
	printf("┏");
	MOVETO(50,5);
	printf("┓");
	MOVETO(10,22);
	printf("┗");
	MOVETO(50,22);
	printf("┛");
}

/*creat new snake_node
 *
 *parameter:
 *
 *return: PSnake p
 *
 */
PSnake creat_snake_node()
{
	PSnake p;
	p = (PSnake)malloc(sizeof(Snake));
	return p;
}
/*creat new food node
 *
 *parameter:
 *
 *return: PFood p
 *
 */
PFood creat_food_node()
{
	PFood p;
	p = (PFood)malloc(sizeof(Food));
	return p;
}
/*initialize snake linked list
 *
 *parameter: Psnake head, Psnake shadow
 *
 *return: Psnake head
 *
 */
PSnake init(PSnake head, PSnake shadow)
{
	Snake *p, *q;
	int i;
	srand(time(0));
	head = creat_snake_node();				//creat and initialize head node
	head -> date.x = 21;
	head -> date.y = 8;
	head -> date.dir = LEFT;
	q = head;
	for(i = 1 ; i <=2 ; i ++)				//creat and initialize second third node
	{
		p = creat_snake_node();
		q -> pnext = p;
		q = p;
		q -> date.x = 21 - 2 * i;
		q -> date.y = 8;
		q -> date.dir = LEFT;
	}
	q -> pnext = NULL;						
	shadow -> date.x = 0;					//initialize shadow node 
	shadow -> date.y = 0;
	return head;
}
/*creat food coordinates
 *
 *parameter: PFood food_head, PSnake snake_head
 *
 *return: PFood p
 *
 */
PFood mkae_food(PFood food_head, PSnake snake_head)
{
	PFood f_p;
	PSnake s_p;

	f_p = food_head;
	while(f_p != NULL)
	{
		f_p -> date.x = (rand() % 20 + 5) * 2 + 1;     //odd number
		f_p -> date.y = rand() % 16 + 6;
		f_p -> date.pic = rand()%4;
		s_p = snake_head;
		while(s_p != NULL)							//avoid food overlao snake
		{
			if((f_p -> date.x == s_p -> date.x) && (f_p -> date.y == s_p -> date.y))
			break;
			s_p = s_p -> pnext;
		}
		if(s_p == NULL) f_p = f_p -> pnext;	
	}
	return food_head;
}
/*initialize snake linked list
 *
 *parameter: PFood food_head, PSnake snake_head
 *
 *return: PFood p
 *
 */
PFood init_food(PFood food_head, PSnake snake_head)
{

	food_head = creat_food_node();
	food_head = mkae_food(food_head, snake_head);

	return food_head;

}
/*display snake and food
 *
 *parameter: PSnake snake_head, PSnake shadow, PFood food_head
 *
 *return:
 *
 */
void draw(PSnake snake_head, PSnake shadow, PFood food_head)
{
	PSnake s_p, tail;
	PFood f_p;
	int i;
	s_p = snake_head;						//find tail snake node
	while(s_p -> pnext != NULL)
		s_p = s_p -> pnext;
	tail = s_p;						

	s_p = snake_head;
	while(s_p != NULL)						//draw snake body
	{
		MOVETO(s_p -> date.x, s_p -> date.y);
		printf("*");
		s_p = s_p -> pnext;
	}

	switch(tail -> date.dir)				//draw snake head
	{
		case -1:
				MOVETO(tail -> date.x, tail -> date.y);
				printf("*");
				//printf("↑");
				break;
		case 1:
				MOVETO(tail -> date.x, tail -> date.y);
				printf("*");
				//printf("↓");
				break;
		case 2:
				MOVETO(tail -> date.x, tail -> date.y);
				printf("*");
				//printf("←");
				break;
		case 4:
				MOVETO(tail -> date.x, tail -> date.y);
				printf("*");
				//printf("→");
				break;
	}
	if(gameover == 0 && shadow_flag == 0)			//erase shadow
	{
		if(shadow -> date.x != 0)
		{
			MOVETO(shadow -> date.x, shadow -> date.y);
			printf(" ");
		}
	}
	else shadow_flag = 0;

	f_p = food_head;
	while(f_p != NULL)								//display food
	{
		if(f_p -> date.x != 0){
		MOVETO(f_p -> date.x, f_p -> date.y);
		printf("%s",FOOD_PIC[f_p -> date.pic]);}
		f_p = f_p -> pnext;
	}
	MOVETO(24,4);
	printf("Scores:%d", scores);
}

/*Collision determination
 *
 *parameter: Snake snake_head_1, PSnake snake_head
 *
 *return: 
 *
 */
void wall(Snake snake_head_1, PSnake snake_head)
{
	PSnake s_p;
												//wall collision
	if(snake_head_1.date.x < 10 || snake_head_1.date.x > 50 || snake_head_1.date.y <= 5 || snake_head_1.date.y >= 22)
		gameover = 1;

	s_p = snake_head -> pnext;
	while(s_p != NULL)							//snake body collision
	{
		if((snake_head_1.date.x == s_p -> date.x) && (snake_head_1.date.y == s_p -> date.y))
			{		
				gameover = 1;
				break;
			}
		s_p = s_p -> pnext;
	}
	
}

/*move snake,change snake link value
 *
 *parameter: PSnake snake_head, PSnake shadow, PFood food_head
 *
 *return: 
 *
 */
void move(PSnake snake_head, PSnake shadow, PFood food_head)
{
	Snake snake_head_1;
	PSnake s_p, tail, new_node;
	PFood f_p;

	shadow -> date.x = snake_head -> date.x;  			 //save shadow value
	shadow -> date.y = snake_head -> date.y;

	s_p = snake_head;                 					 //find tail snake node
	while(s_p -> pnext != NULL)
		s_p = s_p -> pnext;
	tail = s_p;

	snake_head_1 = *s_p;               					//snake head copy move  
	if(cur_dir == LEFT || cur_dir == RIGHT)
		snake_head_1.date.x += (cur_dir-3) * 2;
	else
		snake_head_1.date.y += cur_dir;
	snake_head_1.date.dir = cur_dir;

	wall(snake_head_1, snake_head);
	if(gameover == 0)
	{
		f_p = food_head;
		while(f_p != NULL)           				// find snake food collision node
		{
			if( (snake_head_1.date.x == f_p ->date.x) && (snake_head_1.date.y == f_p -> date.y))
				break;
			f_p = f_p ->pnext;
		}
		if (f_p == NULL)             				//no collision node
		{
			s_p = snake_head;             			 //snake body move
			while(s_p -> pnext != NULL)
			{
				s_p -> date.x = s_p -> pnext -> date.x;
				s_p -> date.y = s_p -> pnext -> date.y;
				s_p = s_p -> pnext;
			}

			tail -> date.x = snake_head_1.date.x;      //snake head move
			tail -> date.y = snake_head_1.date.y;
			tail -> date.dir = snake_head_1.date.dir;
		}
		else                             			 //creat new snake node
		{
			shadow_flag = 1;
			new_node = creat_snake_node();
			tail -> pnext = new_node;
			new_node -> pnext = NULL;
			new_node -> date.x = snake_head_1.date.x;      
			new_node -> date.y = snake_head_1.date.y;
			new_node -> date.dir = snake_head_1.date.dir;
			scores += ((f_p -> date.pic) + 1) * 10;
			f_p = food_head;
			while(f_p != NULL)                          //diappear old food node
			{
				MOVETO(f_p -> date.x, f_p -> date.y);
				printf(" ");
				f_p = f_p -> pnext;
			}

			food_head = mkae_food(food_head, snake_head);  //make new food
			snake_length++;
		}

	}
}
/*terminal setting no echo no wait
 *
 *parameter: 
 *
 *return: 
 *
 */
void keyinit()
{
	tcgetattr(STDIN_FILENO, &oldt);
	newt = oldt;
	newt.c_lflag &= ~(ICANON | ECHO);
	tcsetattr(STDIN_FILENO, TCSANOW, &newt);
	oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
	fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
}

/*recovery terminal setting
 *
 *parameter: 
 *
 *return: 
 *
 */
void Restorekey()
{
	tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
	fcntl(STDIN_FILENO, F_SETFL, oldf);
}
/*game difficulty,exceed 100 scores 
 *creat new food node and speed up
 *
 *parameter: PFood food_head
 *
 *return: PFood p
 *
 */
PFood game_level(PFood food_head)
{
	PFood new_node, f_p;
	f_p = food_head;
	while(f_p -> pnext != NULL)
		f_p = f_p -> pnext;

	switch(scores/100)
	{
		case 1:
				speed = 7;							//speed up
				if(food_length == 1)
				{
					new_node = creat_food_node();	//craet new food node
					new_node -> date.x = 0;
					new_node -> date.y = 0;
					new_node -> date.pic = 0;
					new_node -> pnext = NULL;
					f_p -> pnext = new_node;
					food_length = 2;
				}		
				break;
		case 2:
				speed = 4;
				break;
		case 3:
				speed = 2;
				break;
		default:
				break;
	}
	return food_head;
}
/*print info to debug
 *
 *parameter: PSnake snake_head, PFood food_head
 *
 *return:
 *
 */
void print_info(PSnake snake_head, PFood food_head)
{
	PSnake s_p;
	PFood f_p;
	int i;
	MOVETO(0,23);
	for (i = 1, s_p = snake_head ; s_p != NULL ; ++i, s_p = s_p -> pnext)
	{
		printf("s(%d) x=%d y=%d\n", i, s_p -> date.x, s_p -> date.y);
	}
	
	MOVETO(17,23);
	for (i = 1, f_p = food_head ; f_p != NULL ; ++i, f_p = f_p -> pnext)
	{
		printf("f(%d) x=%d y=%d k=%d", i, f_p -> date.x, f_p -> date.y, f_p -> date.pic);
	}
	MOVETO(17,25);
	printf("speed=%d ",speed);
	MOVETO(25,25);
	printf("s_l%d ",snake_length);
	MOVETO(35,25);
	printf("f_l%d ",food_length);
	MOVETO(17,26);
	printf("dir=%d ",cur_dir);
	MOVETO(24,26);
	printf("over=%d ",gameover);
	MOVETO(31,26);
	printf("scores=%d ",scores);
}
/*save date to snake.dat
 *
 *parameter: PSnake snake_head, PFood food_head
 *
 *return:
 *
 */
void save(PSnake snake_head, PFood food_head)
{
	FILE *fp;
	int i;
	PSnake s_p;
	PFood f_p;

	s_p = snake_head;
	f_p = food_head;
	if((fp = fopen("snake.dat","w")) == NULL)
	{
		printf("open fail\n");
	}
	else
	{
		fwrite("Snake",5,1,fp);				//save tag
		fwrite(&snake_length, 4, 1, fp);   	//save snake node date
		for(i = 0 ; i < snake_length ; i++)
		{
			fwrite( &(s_p -> date), sizeof(Date),1, fp);
			s_p = s_p -> pnext;
		}

		fwrite(&food_length, 4, 1, fp);     //save food node date
		for(i = 0 ; i < food_length ; i++)
		{
			fwrite( &(f_p -> date), sizeof(FDate),1, fp);
			f_p = f_p -> pnext;
		}
		fwrite(&scores, 4 ,1, fp);			//save scores
		fwrite(&cur_dir, 4, 1, fp);			//save current direction
		fwrite(&speed,4,1,fp);				//save speed
	}
	if(fp != NULL)
	{
		fclose(fp);
		fp = NULL;
	}
}
/*load snake link date from snake.dat
 *
 *parameter: PSnake snake_head
 *
 *return:PSnake head
 *
 */
PSnake load_snake(PSnake snake_head)
{
	FILE *fp;
	PSnake s_p, s_new_node, s_p_tail;
	int i;
	char tag[5];

	s_p = snake_head;							//find tail snake node
	while(s_p -> pnext != NULL)
		s_p = s_p -> pnext;
	s_p_tail = s_p;
	if((fp = fopen("snake.dat","r")) == NULL)
	{
		printf("open fail\n");
	}
	else
	{                  
		fread(tag, 5, 1, fp);						//check
		if(strcmp(tag,"Snake") != 0)
			return NULL;

		s_p = snake_head;   
		fread(&snake_length, 4, 1, fp);				//load snake node date
		for(i = 0 ; i < 3 ; i++)
		{
			fread( &(s_p -> date), sizeof(Date), 1, fp);
			s_p = s_p ->pnext;
		}
		for(i = 3 ; i < snake_length ; i++)
		{
			s_new_node = creat_snake_node();
			s_p_tail ->pnext = s_new_node;
			s_new_node -> pnext = NULL;
			fread( &(s_new_node -> date), sizeof(Date), 1, fp);
			s_p_tail = s_new_node;
		}
	}
	if(fp != NULL)
	{
		fclose(fp);
		fp = NULL;
	}
	return snake_head;
}
/*load food link ,scores,dir,speed date from snake.dat
 *
 *parameter: PSnake snake_head
 *
 *return:PSnake head
 *
 */
PFood load_food(PSnake snake_head, PFood food_head)
{
	FILE *fp;
	PFood f_p, f_new_node, f_p_tail;
	int i;

	if((fp = fopen("snake.dat","r")) == NULL)
	{
		printf("open fail\n");
	}
	else
	{
		if(snake_head == NULL)          		 //check
			return NULL;

		f_p = food_head;
		fseek(fp,sizeof(Date) * snake_length + 9, 0); //find right location

		fread(&food_length, 4, 1, fp);
		fread( &(f_p -> date), sizeof(FDate), 1, fp);	//load food link date
		f_p_tail = food_head;

		for (i = 1 ; i < food_length ; i++)
		{
			f_new_node = creat_food_node();
			f_p_tail ->pnext = f_new_node;
			fread( &(f_new_node -> date), sizeof(FDate), 1, fp);
			f_p_tail = f_new_node;
			f_p_tail -> pnext = NULL;

		}
		fread(&scores, 4 ,1, fp);				//load scores
		fread(&cur_dir, 4, 1, fp);				//load current direction
		fread(&speed,4,1,fp);					//load speed
	}

	if(fp != NULL)
	{
		fclose(fp);
		fp = NULL;
	}
	return food_head;
}
/*select motion
 *
 *parameter: PSnake snake_head, PFood food_head
 *
 *return:
 *
 */
void key_dir(PSnake snake_head, PFood food_head)
{
	char ch;
	PSnake p, tail;

	if((ch = getchar()) != 0)
		switch(ch)
		{
			case 'w':cur_dir = UP;break;
			case 'a':cur_dir = LEFT;break;
			case 's':cur_dir = DOWN;break;
			case 'd':cur_dir = RIGHT;break;
			case 'p':
					MOVETO(15,4);
					printf("PAUSE ENTER ANY KEY TO CONTINUE");
					Restorekey();
					getchar();
					keyinit();
					MOVETO(15,4);
					printf("                                ");
					break;
			case 'q':
					save(snake_head, food_head);
					gameover = 1;
					break;
		}
}

/*display menu choice new game or load game
 *
 *parameter:
 *
 *return:
 *
 */
int menu()
{
	FILE *fp;
	int num;
	MOVETO(17,10);
	printf("input a number to start game");
	MOVETO(24,11);
	printf("1.NEW GAME");
	if((fp = fopen("snake.dat","r")) == NULL)
	{
		MOVETO(24,12);
		printf(L_BLACK "2.LOAD GAME");			//no snake.dat
		printf(	NONE "");
		do
		{
			scanf("%d", &num);
			MOVETO(25,13);
			printf("        ");
			if(num == 2)
			{
				MOVETO(25,13);
				printf("NO SAVE");
			}
		}
		while(num != 1);
	}
	else
	{
		MOVETO(24,12);
		printf("2.LOAD GAME");					//exist snake.dat
		do
			scanf("%d", &num);
		while(num != 1 && num != 2);
	}

	MOVETO(17,10);
	printf("                             ");
	MOVETO(24,11);
	printf("            ");
	MOVETO(24,12);
	printf("            ");

	if(fp != NULL)
	{
		fclose(fp);
		fp = NULL;
	}

	if(num == 2)
		return 1;
	else 
		return 0;
}
/*free snake link and food link
 *
 *parameter: PSnake snake_head, PFood food_head
 *
 *return:
 *
 */
void free_node(PSnake snake_head, PFood food_head)
{
	PSnake p, q;
	PFood pi, qi;
	p = snake_head;						//free snake link
	while(p)
	{
		q = p;
		p = p -> pnext;
		free(q);
	}

	pi = food_head;						//free food link
	while(pi)
	{
		qi = pi;
		pi = pi -> pnext;
		free(qi);
	}

}

int  main()
{
	PSnake snake_head;
	Snake snake_shadow;
	PFood food_head;
	int i = 0;

	snake_head = init(snake_head, &snake_shadow);
	food_head = init_food(food_head, snake_head);
	keyinit();
	draw_ground();

	if(menu())
	{
		snake_head = load_snake(snake_head);
		food_head = load_food(snake_head, food_head);
	}
	if(snake_head == NULL)
	{
		Restorekey();
		free_node(snake_head, food_head);
		return 0;
	}
	while(1)
	{
		key_dir(snake_head, food_head);
	//	print_info(snake_head, food_head);
		food_head = game_level(food_head);
		if(i % speed == 0)
		{
			move(snake_head, &snake_shadow, food_head);
			draw(snake_head, &snake_shadow, food_head);
			i = 0;
		}
		usleep(1000*1000/10);
		if(gameover == 1)
		{
			Restorekey();
			free_node(snake_head, food_head);
			break;
		}
		MOVETO(0,28);
		i++;
	}
	return 0;

}
		